import React, { useRef } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import type { DragSourceMonitor, DropTargetMonitor } from 'react-dnd';
import type { Dispatch } from 'umi';
import { useDispatch, useSelector } from 'react-redux';
import { v1 as uuid } from 'uuid';
import { isParentNode } from '@/components/editor/utils/index';
import { merge, get, debounce, throttle, cloneDeep, isEmpty } from 'lodash';

interface DragItem {
  type: string;
  data: any;
  dragParentId: string;
  dragIndex: number;
}

class DragDrop {
  /** redux dispatch */
  private dispatch: Dispatch<any>;

  /** 布局配置 */
  dataCenter: any;

  /** 拖拽源ref */
  dragRef = null;

  /** 放置目标ref */
  dropRef = null;

  /** 拖拽高亮 */
  dragHighlight = {
    pid: null,
    mode: null,
    id: null,
  };

  /** 组件配置 */
  data = null;

  /** 当前聚焦组件id */
  focusId;

  /** dnd Type */
  type = 'ItemTypes.Page';

  setupValue({
    dispatch,
    dataCenter,
    focusId,
    data,
    dragRef,
    dropRef,
    dragHighlight,
  }) {
    this.dispatch = dispatch;
    this.dataCenter = dataCenter;
    this.focusId = focusId;
    this.data = data;
    this.dragRef = dragRef;
    this.dropRef = dropRef;
    this.dragHighlight = dragHighlight || { pid: null, mode: null, id: null };
  }

  begin(monitor: DragSourceMonitor) {
    const draggingItem = {
      data: cloneDeep(this.data),
      fixedY: 0,
    };

    if (get(this.dragRef, 'current.getBoundingClientRect')) {
      draggingItem.fixedY =
        monitor.getInitialClientOffset().y -
        this.dragRef.current.getBoundingClientRect().y;
    }

    return draggingItem;
  }

  end(item, monitor: DragSourceMonitor) {}

  getDragHighlight({ source, target, cmpHeight, deltaY }) {
    // console.log("🚀 ~ file: dragDrop.ts ~ line 82 ~ DragDrop ~ getDragHighlight ~ target", target)

    const targetId = target.id;
    /**
     * 拖拽高亮处理
     * top      组件上
     * middle   组件内（仅容器组件支持）
     * bottom   组件下
     */
    const dragHighlight = {
      id: targetId,
      mode: 'top',
      pid: target.pid,
    };

    const isBottom = this.dropRef.current.offsetHeight / 2 < deltaY;
    if (isBottom) {
      dragHighlight.mode = 'bottom';
    }

    return dragHighlight;
  }

  /** 放置组件 悬浮回调 */
  hover = throttle(
    (item, monitor: DropTargetMonitor) => {
      const { data: source } = item;
      const target = this.data;
      // console.log("🚀 ~ file: dragDrop.ts ~ line 110 ~ DragDrop ~ target", target)

      const isOverCurrent = monitor.isOver({ shallow: true });
      const sourceClientOffset = monitor.getSourceClientOffset();

      // 拖拽源 与 拖拽目标 相同时，不做标记处理
      if (
        !item ||
        !source ||
        source.id === target.id ||
        !isOverCurrent ||
        !sourceClientOffset
      )
        return;

      // 拖拽目标 没有，不处理
      if (!this.dropRef || !this.dropRef.current) return;

      let deltaY = sourceClientOffset.y + item.fixedY;

      if (get(this.dropRef, 'current.getBoundingClientRect')) {
        deltaY -= this.dropRef.current.getBoundingClientRect().y;
      }

      if (deltaY < 1) return;

      const cmpHeight = this.dropRef.current.offsetHeight; // 组件高度

      // 设置高亮
      const dragHighlight = this.getDragHighlight({
        source,
        target,
        cmpHeight,
        deltaY,
      });
      console.log(
        '🚀 ~ file: dragDrop.ts ~ line 133 ~ DragDrop ~ dragHighlight',
        dragHighlight,
      );

      this.dispatch({
        type: 'layout/setDragHighlight',
        payload: dragHighlight,
      });
    },
    100,
    { leading: false },
  );

  /** react-dnd useDrag spec 包装方法 */
  useDrag() {
    return {
      type: 'CRAD',
      /** 当拖动开始时，beginDrag被调用。如果从该函数返回一个对象，它将取代默认的dragItem。 */
      item: this.begin.bind(this),
      /**
       * 当拖动停止时，endDrag被调用。对于每一个beginDrag的调用，都保证有一个相应的endDrag调用。
       * 你可以调用monitor.didDrop()来检查拖动是否被一个兼容的拖动目标所处理。如果它被处理了。
       * 并且下落目标通过从其drop()方法中返回一个普通对象来指定下落结果，它将作为
       * monitor.getDropResult()。这个方法是一个启动Flux动作的好地方。注意：如果组件在拖动时被卸载。
       * 组件参数被设置为空。
       */
      end: this.end.bind(this),
      /** 一个用于收集渲染属性的函数 */
      collect: (monitor) => {
        return {
          isDragging: monitor.isDragging(),
          initialOffset: monitor.getInitialSourceClientOffset() || {},
          currentOffset: monitor.getSourceClientOffset() || {},
        };
      },
    };
  }

  useDrop() {
    return {
      accept: 'CRAD',
      // drop: (item, monitor) => {
      //   const { data: source } = item;
      //   const target = this.data;
      //   const didDrop = monitor.didDrop(); // returns false for direct drop target
      //   if (didDrop) {
      //     return;
      //   }

      //   if (!source.id && target.data.id == 'root') {
      //     this.dispatch({
      //       type: 'layout/addAppend',
      //       payload: clonedeep(item),
      //     });
      //   } else if (!source.id) {
      //     //  console.log("xxxxxx");
      //   } else {
      //   }

      //   //没有id 是新增,有id是移动
      //   return { name: 'Dustbin' };
      // },
      collect: (monitor) => ({
        isOver: monitor.isOver({
          shallow: true,
        }),
        canDrop: monitor.canDrop(),
      }),
      hover: this.hover.bind(this),
      drop: this.drop.bind(this),
    };
  }

  /** 放置组件 放置回调 */
  drop(item, monitor) {
    // console.log("🚀 ~ file: dragDrop.ts ~ line 214 ~ DragDrop ~ drop ~ item", item)
    const { data: source } = item;
    const target = this.data;

    if (monitor.didDrop()) {
      return;
    }

    const payload: any = {
      source,
      target,
      dragHighlight: this.dragHighlight,
    };

    this.moveInLayout(payload);
  }

  /** 处理组件拖拽逻辑 */
  moveInLayout(payload) {
    const { source, target, dragHighlight } = payload;
    console.log(
      '🚀 ~ file: dragDrop.ts ~ line 241 ~ DragDrop ~ moveInLayout ~ target',
      target,
    );
    // console.log("🚀 ~ file: dragDrop.ts ~ line 231 ~ DragDrop ~ moveInLayout ~ source", source)

    // console.log("🚀 ~ file: dragDrop.ts ~ line 229 ~ DragDrop ~ moveInLayout ~ dragHighlight", dragHighlight)
    const dataCenter = cloneDeep(this.dataCenter);
    let focusId = '';

    if (source.id) {
      focusId = source.id;
    } else {
      focusId = uuid();
    }

    this.focusId = focusId;

    if (!isParentNode(target.type)) {
      let index = dataCenter[target.pid].sons.findIndex((son) => {
        return son === target.id;
      });
      if (!source.id) {
        if (dragHighlight.mode == 'bottom') {
          dataCenter[target.pid].sons.splice(index + 1, 0, focusId);
        } else {
          dataCenter[target.pid].sons.splice(index, 0, focusId);
        }
        dataCenter[focusId] = { ...source, id: focusId, pid: target.pid };
      } else {
        let hoverIndex = index;
        let dragIndex = dataCenter[source.pid].sons.findIndex((son) => {
          return son === source.id;
        });

        //同级 => 交换
        if (source.pid == target.pid) {
          function moveIndex(arr, index, tindex) {
            //如果当前元素在拖动目标位置的下方，先将当前元素从数组拿出，数组长度-1，我们直接给数组拖动目标位置的地方新增一个和当前元素值一样的元素，
            //我们再把数组之前的那个拖动的元素删除掉，所以要len+1
            if (index > tindex) {
              arr.splice(tindex, 0, arr[index]);
              arr.splice(index + 1, 1);
            } else {
              //如果当前元素在拖动目标位置的上方，先将当前元素从数组拿出，数组长度-1，我们直接给数组拖动目标位置+1的地方新增一个和当前元素值一样的元素，
              //这时，数组len不变，我们再把数组之前的那个拖动的元素删除掉，下标还是index
              arr.splice(tindex + 1, 0, arr[index]);
              arr.splice(index, 1);
            }
          }

          if (Math.abs(dragIndex - hoverIndex) == 1) {
            if (
              (dragHighlight.mode == 'top' && dragIndex > hoverIndex) ||
              (dragHighlight.mode == 'bottom' && dragIndex < hoverIndex)
            ) {
              moveIndex(dataCenter[target.pid].sons, dragIndex, hoverIndex);
            }
          } else {
            moveIndex(dataCenter[target.pid].sons, dragIndex, hoverIndex);
          }
        } else {
          // 1、删除原节点
          dataCenter[source.pid].sons.splice(dragIndex, 1);
          // 2、插入新节点
          dataCenter[focusId].pid = target.pid;
          if (dragHighlight.mode == 'bottom') {
            dataCenter[target.pid].sons.splice(index + 1, 0, focusId);
          } else {
            dataCenter[target.pid].sons.splice(index, 0, focusId);
          }
        }
      }

      // console.log("🚀 ~ file: dragDrop.ts ~ line 238 ~ DragDrop ~ moveInLayout ~ dataCenter", dataCenter)
    } else {
      if (source.pid) {
        let dragIndex = dataCenter[source.pid].sons.findIndex((son) => {
          return son === source.id;
        });
        dataCenter[source.pid].sons.splice(dragIndex, 1);
      }
      dataCenter[focusId] = {
        ...source,
        id: focusId,
        pid: target.id,
      };
      dataCenter[target.id].sons.push(focusId);
    }

    this.dispatch({
      type: 'layout/addAppend',
      payload: {
        dataCenter,
        focusId,
      },
    });
  }
}

const useDragDrop = ({
  data,
  dragRef,
  dropRef,
}: {
  data;
  dragRef?;
  dropRef?;
}) => {
  const dispatch = useDispatch();
  const { state } = useSelector((_) => ({
    state: _,
  }));
  const { focusId, dataCenter, dragHighlight } = state['layout'];

  const dragDropRef = useRef(new DragDrop());

  dragDropRef.current.setupValue({
    dispatch,
    dataCenter,
    focusId,
    data,
    dragRef,
    dropRef,
    dragHighlight,
  });
  return dragDropRef.current;
};

export default DragDrop;

export { useDragDrop, useDrag, useDrop };
